package nacos

import (
    "bytes"
    "errors"
    "fmt"
    log "github.com/sirupsen/logrus"
    "io/ioutil"
    "net"
    "net/http"
    "strings"
    "sync/atomic"
    "time"
)

const (
    DEFAULT_CONNECT_TIMEOUT         = 30 * time.Second
    DEFAULT_MAX_IDLE_CONNS          = 512
    DEFAULT_MAX_IDLE_CONNS_PER_HOST = 10
    //
    DEFAULT_IDLE_CONN_TIMEOUT       = 30 * time.Second
    DEFAULT_RESPONSE_HEADER_TIMEOUT = 10 * time.Second
    DEFAULT_KEEP_ALIVE              = 30 * time.Second
    DEFAULT_EXPECT_CONTINUE_TIMEOUT = 10 * time.Second
)

type NacosClient struct {
    Servers []string
    Client  *http.Client
    lastCt  uint32
}

func NewNacosClient(servers string) *NacosClient {
    c := &NacosClient{}
    c.Servers = strings.Split(servers, ",")
    c.Client = c.NewHttpClient(DefaultHttpClientConfig())
    return c
}

//Millisecond
type HttpClientConfig struct {
    ConnectTimeout time.Duration
    KeepAlive      time.Duration
    //
    MaxIdleConns          int
    MaxIdleConnsPerHost   int
    IdleConnTimeout       time.Duration
    ResponseHeaderTimeout time.Duration
    ExpectContinueTimeout time.Duration
}

func DefaultHttpClientConfig() *HttpClientConfig {
    hcc := &HttpClientConfig{}
    hcc.MaxIdleConns = DEFAULT_MAX_IDLE_CONNS
    hcc.MaxIdleConnsPerHost = DEFAULT_MAX_IDLE_CONNS_PER_HOST
    hcc.ConnectTimeout = DEFAULT_CONNECT_TIMEOUT
    hcc.ResponseHeaderTimeout = DEFAULT_RESPONSE_HEADER_TIMEOUT
    hcc.KeepAlive = DEFAULT_KEEP_ALIVE
    hcc.IdleConnTimeout = DEFAULT_IDLE_CONN_TIMEOUT
    hcc.ExpectContinueTimeout = DEFAULT_EXPECT_CONTINUE_TIMEOUT
    return hcc
}
func (h *NacosClient) NewHttpClient(hcc *HttpClientConfig) *http.Client {
    //http client设置
    transport := &http.Transport{
        //Dial: func(network, addr string) (net.Conn, error) {
        //    //deadline := time.Now().Add(10 * time.Second)
        //    c, err := net.DialTimeout(network, addr, hcc.ConnectTimeout) //设置建立连接超时
        //    if err != nil {
        //        return nil, err
        //    }
        //    //c.SetDeadline(deadline) //不建议，设置发送接收数据超时
        //    return c, nil
        //},
        DialContext: (&net.Dialer{
            Timeout:   hcc.ConnectTimeout, // 30s  time.Duration(config.ConnectTimeout) * time.Millisecond,
            KeepAlive: hcc.KeepAlive,      //30s,
            DualStack: true,
        }).DialContext,
        MaxIdleConns:          hcc.MaxIdleConns,          //100,最大空闲(keep-alive)连接，
        MaxIdleConnsPerHost:   hcc.MaxIdleConnsPerHost,   //2,每host最大空闲(keep-alive)连接，=0则DefaultMaxIdleConnsPerHost=2
        IdleConnTimeout:       hcc.IdleConnTimeout,       //90s,90 * time.Second,
        ExpectContinueTimeout: hcc.ExpectContinueTimeout, //10s,time.Second * 2,
        ResponseHeaderTimeout: hcc.ResponseHeaderTimeout, //
        TLSHandshakeTimeout:   1 * time.Second,           //1s
    }
    httpClient := &http.Client{
        Transport: transport,
    }

    return httpClient
}

func (h *NacosClient) Next() string {

    nv := atomic.AddUint32(&h.lastCt, 1)
    size := len(h.Servers)
    if size == 0 {
        panic(errors.New("not found client."))
    }
    index := int(nv) % size
    selected := h.Servers[index]
    return selected
}

func (h *NacosClient) Get(endpoint string) (statusCode int, body []byte, err error) {
    //调用请求
    return h.do(endpoint, func(url string) (resp *http.Response, err error) {
        return h.Client.Get(url)
    })
}

func (h *NacosClient) Delete(endpoint string) (statusCode int, body []byte, err error) {
    //调用请求
    return h.do(endpoint, func(url string) (resp *http.Response, err error) {
        r, err := http.NewRequest(http.MethodDelete, url, nil)
        if err != nil {
            return nil, err
        }
        return h.Client.Do(r)
    })
}

func (h *NacosClient) Post(endpoint, contentType string, content []byte) (statusCode int, body []byte, err error) {
    //调用请求
    return h.do(endpoint, func(url string) (resp *http.Response, err error) {
        return h.Client.Post(url, contentType, bytes.NewBuffer(content))
    })
}

func (h *NacosClient) do(endpoint string, fun func(url string) (resp *http.Response, err error)) (statusCode int, body []byte, err error) {
    base := h.Next()
    url := fmt.Sprintf("http://%s%s", base, endpoint)

    //调用请求
    res, err := fun(url)

    if err != nil {
        log.Error(err)
        return 0, nil, err
    }
    // 如果出错就不需要close，因此defer语句放在err处理逻辑后面
    defer res.Body.Close()
    //处理response,读取Response body
    respBody, err := ioutil.ReadAll(res.Body)

    //
    if err := res.Body.Close(); err != nil {
        log.Error(err)
    }
    return res.StatusCode, respBody, err
}
